#include "filesys/filesys.h"
#include <debug.h>
#include <stdio.h>
#include <string.h>
#include "filesys/file.h"
#include "filesys/free-map.h"
#include "filesys/inode.h"
#include "filesys/directory.h"
#include "devices/disk.h"
// 3 idiots added begin
#include "filesys/cache.h"
#include "threads/malloc.h"
#include "threads/thread.h" 
// 3 idiots added end

/* The disk that contains the file system. */
struct disk *filesys_disk;

static void do_format (void);

/* Initialize the file system module.
   If FORMAT is true, reformats the file system. */
void
filesys_init (bool format) 
{
  filesys_disk = disk_get (0, 1);
  if (filesys_disk == NULL)
    PANIC ("hd0:1 (hdb) not present, file system initialization failed");

  inode_init ();
  free_map_init ();
  
  // 3 idiots added begin
  cache_init (); 
  thread_set_current_dir (dir_open_root ());
  // 3 idiots added end 

  if (format) 
    do_format ();

  free_map_open ();
}

/* Shuts down the file system module, writing any unwritten data
   to disk. */
void
filesys_done (void) 
{
  // 3 idiots added begin
  cache_flush_sector (); 
  // 3 idiots added end
  free_map_close ();
}

/* Creates a file named NAME with the given INITIAL_SIZE
   Returns true if successful, false otherwise.
   Fails if a file named NAME already exists,
   or if internal memory allocation fails. */
bool filesys_create (const char *name, off_t initial_size, bool arg_is_dir)  
{
    bool success = false;
    char *path = NULL;
    char *file_name = NULL;

    if (strlen (name) > NAME_MAX)
    {
	return false;
    }

    if (name != NULL && strcmp (name, "") != 0 && dir_get_dir_path_and_file_name (name, &path, &file_name))
    {
	struct dir* parent = filesys_dir_get_by_path (path);
	struct inode *parent_inode = dir_get_inode (parent);

	struct inode* check;

	if (parent != NULL && !dir_lookup (parent, name, &check))
	{
	    // allocate disk sector
	    disk_sector_t sector;
            if (!free_map_allocate (1, &sector))
            {
               return false;
            }

            if (arg_is_dir)
	    {
                // dir create
                if (!dir_create (sector, parent_inode->sector))
                {
                   return false;
                }
	    }
	    else
	    {
		// file create
                if (!inode_create (sector, initial_size, false))
                {
                   return false;
                }
	    }
	    dir_add (parent, file_name, sector);
	    success = true;
	 }
	 else
	 {
	    inode_close (check);
	 }

	 dir_close (parent);
		
	 free (path);
	 free (file_name);
     }
     return success;
}

/* Opens the file with the given NAME.
   Returns the new file if successful or a null pointer
   otherwise.
   Fails if no file named NAME exists,
   or if an internal memory allocation fails. */
struct file* filesys_open (const char *name)
{
   if (name == NULL || strlen (name) == 0)
   {
      return NULL;
   }
   
   char *path = NULL;
   char *file = NULL;
   dir_get_dir_path_and_file_name (name, &path, &file);
   struct dir* target_dir = filesys_dir_get_by_path (path);
   if (target_dir != NULL)
   {
      return filesys_dir_get_file (target_dir, file);
   }
   else
   {
      return NULL;
   }
}

/* Deletes the file named NAME.
   Returns true if successful, false on failure.
   Fails if the file named NAME dose not exist,
   or if an internal memory allocation fails. */
bool filesys_remove (const char *name) 
{
   struct file* file = filesys_open (name);

   if (file == NULL)
   {
      return false;
   }

   bool success = false;
   char *parent_path = NULL;
   char *file_path = NULL;
   dir_get_dir_path_and_file_name (name, &parent_path, &file_path);	
	
   struct file* parent_file = filesys_dir_get_file_by_path (parent_path);
   struct dir parent = filesys_dir_initialize (parent_file, -1);

   if (!inode_is_dir (parent_file->inode))
   {
      return false;
   }

   if (inode_is_dir (file->inode)) 
   {
      struct dir temp_dir = filesys_dir_initialize (file, -1);
      struct dir* cur_dir = thread_get_current_dir ();
      
      disk_sector_t sector = file->inode->sector;
      disk_sector_t work_dir_sec = cur_dir->inode->sector;
      disk_sector_t root_sec = dir_open_root ()->inode->sector;

      if (sector == work_dir_sec || sector == root_sec)
      {	
	 return false;
      }
      else 
      {
          if (dir_none (&temp_dir))
	  {
            file_close (file);
	
  	    if (temp_dir.inode->open_cnt <= 1)
	    {
               if (!dir_remove (&parent, file_path))
               {
                  file_close (parent_file);
                 return false;
               }
	       inode_remove (file->inode);
	       success = true;
	    }
	    if (parent_file->inode->open_cnt > 1)
	       file_close (parent_file);
          }  
       }
    }
    else
    {	
        if (!dir_remove (&parent, file_path))
        {
           file_close (file);
           file_close (parent_file);
           return false;
        }
	inode_remove (file->inode);
	if (parent_file->inode->open_cnt > 1)
        {
	  file_close (parent_file);
        }
        file_close (file);
	success = true;
    }
    free (parent_path);
    free (file_path);
    return success;
}

/* Formats the file system. */
static void
do_format (void)
{
  printf ("Formatting file system...");
  free_map_create ();

  if (!dir_create (ROOT_DIR_SECTOR, ROOT_DIR_SECTOR))
    PANIC ("root directory creation failed");
  free_map_close ();
  printf ("done.\n");
}

struct dir filesys_dir_initialize (struct file* arg_file, int arg_pos)
{
    struct dir temp_dir;
    temp_dir.inode = arg_file->inode;

    if (arg_pos == -1)
    {
       temp_dir.pos = arg_file->pos;
    }
    else
    {
       temp_dir.pos = arg_pos;
    }
    return temp_dir;
}

struct dir* filesys_dir_get_by_path (char* dir_path)
{
    struct dir* temp_dir = NULL;

    if (dir_path == NULL)
    {
       temp_dir = dir_reopen (thread_get_current_dir ());
    }
    else
    {
       temp_dir = dir_get_directory (dir_path);
    }
    return temp_dir;
}

struct file* filesys_dir_get_file (struct dir* arg_dir, char* file_name)
{
    if (strcmp(file_name, "") == 0)
    {
       return file_open (arg_dir->inode);
    }
    else
    {		
       struct inode* temp_file_inode = NULL;
       dir_lookup (arg_dir, file_name, &temp_file_inode);
       dir_close (arg_dir);
       return file_open (temp_file_inode);
    }
}

struct file* filesys_dir_get_file_by_path (char* arg_path)
{
    if (strcmp (arg_path, "") == 0)
    {
       struct dir* temp_dir = thread_get_current_dir ();
       return file_open (temp_dir->inode);
    }
    else
    {
       return filesys_open (arg_path);
    }
}
